package gbench.sandbox.tuple;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class M4 {
    
    public static Stream<Integer> stm(){
        return Stream.iterate(0,i->i+1);
    }

    public static Stream<Integer> stm(final Number maxSize){
        return stm().limit(maxSize.longValue());
    }

    public static Stream<Integer> stm(final Number maxSize, final Predicate<Integer> predicate){
        return stm(maxSize).filter(predicate);
    }

    public static <T> Tuple2<Integer,Integer> shape(final  T[][] tt){
        return new Tuple2<>(tt.length,S(tt).collect(Collectors.summarizingInt(t->t.length)).getMax());
    }

    @SuppressWarnings("unchecked")
    public static <T> IntFunction<T[]> arr(final Class<T> clazz){
        final var tclass = clazz == null? (Class<T>) Object.class: clazz;
        return n->(T[]) Array.newInstance(tclass,n);
    }
    
    @SuppressWarnings("unchecked")
    public static <T> IntFunction<T[][]> arr2(final Class<T> tclazz){
        return n->(T[][])Array.newInstance(tclazz,n,1);
    }

    @SuppressWarnings("unchecked")
    public static <T> T[][] ij_yield(final int m, final int n, BiFunction<Integer,Integer,T> fxy, final Class<?> clazz){
        final var tclass = clazz == null? (Class<T>) Object.class: clazz;
        return stm(m).map(i->stm(n).map(j->fxy.apply(i,j)).toArray(arr(tclass)))
                .toArray(size->(T[][])Array.newInstance(tclass,size,1));
    }
    
    public static <T> T[][] ij_yield(final int m, final int n, final BiFunction<Integer,Integer,T> fxy){
        return ij_yield(m,n,fxy,fxy.apply(0,0).getClass());
    }

    public static <T> T[][] transpose(final T[][] tt){
        final var shape = shape(tt);
        return ij_yield(shape._2,shape._1,(i,j)->tt[j][i],tt.getClass().getComponentType().getComponentType());
    }

    public static <T extends Number,U extends Number> Double dot (final T[] tt ,final U[] uu){
        return Tuple2.zip(tt,uu).map(p->p._1.doubleValue()*p._2.doubleValue()).reduce(0d,(a,b)->a+b);
    }

    public static <T extends Number,U extends Number> Double[] add (final T[] tt ,final U[] uu){
        return Tuple2.zip(tt,uu).map(p->p._1.doubleValue()+p._2.doubleValue()).toArray(arr(Double.class));
    }
    
    public static <T extends Number,U extends Number> Double[] add (final U[] uu,final T t){
        return add(A(t),uu);
    }
    
    public static <T extends Number,U extends Number> Double[] add (final T t ,final U[] uu){
        return add(A(t),uu);
    }

    public static <T extends Number,U extends Number> Double[] sub (final T[] tt ,final U[] uu){
        return Tuple2.zip(tt,uu).map(p->p._1.doubleValue()-p._2.doubleValue()).toArray(arr(Double.class));
    }
    
    public static <T extends Number,U extends Number> Double[] sub (final T t ,final U[] uu){
        return add(A(t),uu);
    }
    
    public static <T extends Number,U extends Number> Double[] sub (final U[] uu,final T t ){
        return add(A(t),uu);
    }
    
    public static <T extends Number,U extends Number> Double[] mul (final T[] tt ,final U[] uu){
        return Tuple2.zip(tt,uu).map(p->p._1.doubleValue()*p._2.doubleValue()).toArray(arr(Double.class));
    }

    public static <T extends Number,U extends Number> Double[] mul (final T t ,final U[] uu){
        return mul(A(t),uu);
    }
    
    public static <T extends Number,U extends Number> Double[] mul (final U[] uu,final T t){
        return mul(A(t),uu);
    }

    public static <T extends Number,U extends Number> Double[] div (final T[] tt ,final U[] uu){
        return Tuple2.zip(tt,uu).map(p->p._1.doubleValue()/p._2.doubleValue()).toArray(arr(Double.class));
    }
    
    public static <T extends Number,U extends Number> Double[] div (final T t ,final U[] uu){
        return add(A(t),uu);
    }
    
    public static <T extends Number,U extends Number> Double[] div2 (final T[] tt ,final U[] uu){
        return Tuple2.zip(uu,tt).map(p->p._1.doubleValue()/p._2.doubleValue()).toArray(arr(Double.class));
    }
    
    public static <T extends Number,U extends Number> Double[] div2 (final T t ,final U[] uu){
        return div2(A(t),uu);
    }

    public static <T extends Number,U extends Number> Double[][] mmult(final T[][] tt, final U[][] uu){
        final var shape = new Tuple2<>(shape(tt)._1,shape(uu)._2);
        final var ss = transpose(uu);
        return ij_yield(shape._1, shape._2,(i,j)->dot(tt[i],ss[j]),Double.class);
    }

    @SuppressWarnings("unchecked")
    public static <T > T[][] minor(final T[][] tt, final int _i, final int _j){
        final var shape = shape(tt);
        final var c1 = (Class<T[]>) tt.getClass().getComponentType();
        final var c2 = c1.getComponentType();
        return stm(shape._1,i->i!=_i).map(i->stm(shape._2,j->j!=_j).map(j->tt[i][j]).toArray(arr(c2))).toArray(arr(c1));
    }

    public static <T extends Number> Double det(final T[][] tt){
        final var shape = shape(tt);
        return shape._2==1 && shape._1 ==1
                ? tt[0][0].doubleValue()
                : stm(shape._1).map(i->tt[i][0].doubleValue()*det(minor(tt,i,0))*(i%2==0?1:-1))
                .reduce(0d,(a,b)->a+b);
    }
    
    @SafeVarargs
    public static <T> Stream<T>  S(T ... ts){
        return Stream.of(ts);
    }

    @SafeVarargs
    public static <T> T[]  A(T ... ts){
        return ts;
    }

    @SafeVarargs
    public static <T> List<T> L(T ... ts){
        return Arrays.asList(ts);
    }
    
    
    @SuppressWarnings("unchecked")
    public static <T> T[] L2A(final List<T> tt){
        final var tclass = tt.stream().filter(e->e!=null)
                .map(e->(Class<T>)e.getClass()).findFirst().orElse((Class<T>)Object.class);
        return tt.toArray(arr(tclass));
    }

    @SuppressWarnings("unchecked")
    public static <T> T[] CONS(final  T t ,final T[] tt){
        return Stream.concat(S(t),S(tt)).toArray(arr((Class<T>)tt.getClass().getComponentType()));
    }
    
    @SafeVarargs
    public static <T> T[][] V(final T ...ts){
        return transpose(RPTA(1,A(ts)));
    }

    public static <T> Function<T[],T[][]> vt2mx(final int m,final int n ){
        return tt->ij_yield(m,n,(i,j)->tt[(j+i*n)%tt.length],tt.getClass().getComponentType());
    }

    public static <T> Function<T[],T[][]> vt2mx2(final int m,final int n ){
        return tt->ij_yield(m,n,(i,j)->tt[(i+j*m)%tt.length],tt.getClass().getComponentType());
    }

    public static <T> Collector<T,List<T>,T[][]> mxclc(final Function<List<T>,T[][]> vt2mx){
        return Collector.of(LinkedList::new,List::add,(aa,bb)->{aa.addAll(bb);return bb;},vt2mx);
    }

    @SuppressWarnings("unchecked")
    public static <T> Collector<T,List<T>,T[][]> mxclc(final int m,final int n){
        return mxclc(tt->(T[][])vt2mx(m,n).apply(L2A(tt)));
    }

    @SuppressWarnings("unchecked")
    public static <T> Collector<T,List<T>,T[][]> mxclc2(final int m,final int n){
        return mxclc(tt->(T[][])vt2mx2(m,n).apply(L2A(tt)));
    }

    public static class Tuple2<T,U>{

        public Tuple2(T _1, U _2) {
            this._1 = _1;
            this._2 = _2;
        }

        public final T _1;
        public final U _2;

        public static <T,U> Stream<Tuple2<T,U>> zip(final T[] tt,final U[] uu){
            return stm(Math.max(tt.length,uu.length)).map(i->new Tuple2<>(tt[i%tt.length],uu[i%uu.length]));
        }
    }

    public static class Elem<T> extends Tuple2<T,Elem<T>>{
        public Elem(T _1, Elem<T> _2) {
            super(_1, _2);
        }

        public Elem(T _1) {
            super(_1,null);
        }

        public static <T> Stream<T> data(final Elem<T> elem){
            return elem._2 == null
                    ? S(elem._1)
                    : Stream.concat(data(elem._2),S(elem._1));
        }

        public Stream<T> data(){
            return data(this);
        }

        public static <T> Elem<T> cons( final T t,final Elem<T> elem){
            return new Elem<>(t,elem);
        }

        public Elem<T> cons(final T t){
            return cons(t,this);
        }
    }

    public static <T> Stream<Elem<T>> cph(final List<T[]> sss){
        return sss.size()<2
                ? S(sss.get(sss.size()-1)).map(Elem::new)
                : cph(sss.subList(0,sss.size()-1)).flatMap(p->S(sss.get(sss.size()-1)).map(p::cons));
    }

    public static <T> Stream<List<T>> cph2(final List<T[]> sss){
        return cph(sss).map(e->e.data().collect(Collectors.toList()));
    }

    public static <T extends Number,U extends Number> Double[][] add(final T[][] tt,final U[][] uu){
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1,shape_u._1),Math.max(shape_t._2,shape_u._2));
        return ij_yield(shape._1,shape._2,
                (i,j)-> tt[i%shape_t._1][j%shape_t._2].doubleValue() + uu[i%shape_u._1][j%shape_u._2].doubleValue(),
                Double.class);
    }
    
    public static <T extends Number,U extends Number> Double[][] add(final T[][] tt,final U u){
        final var uu = RPTA(1,A(u));
        return add(tt,uu);
    }

    public static <T extends Number,U extends Number> Double[][] sub(final T[][] tt,final U[][] uu){
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1,shape_u._1),Math.max(shape_t._2,shape_u._2));
        return ij_yield(shape._1,shape._2,
                (i,j)-> tt[i%shape_t._1][j%shape_t._2].doubleValue() - uu[i%shape_u._1][j%shape_u._2].doubleValue(),
                Double.class);
    }
    
    public static <T extends Number,U extends Number> Double[][] sub(final T[][] tt,final U u){
        final var uu = RPTA(1,A(u));
        return sub(tt,uu);
    }

    public static <T extends Number,U extends Number> Double[][] mul(final T[][] tt,final U[][] uu){
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1,shape_u._1),Math.max(shape_t._2,shape_u._2));
        return ij_yield(shape._1,shape._2,
                (i,j)-> tt[i%shape_t._1][j%shape_t._2].doubleValue() * uu[i%shape_u._1][j%shape_u._2].doubleValue(),
                Double.class);
    }
    
    public static <T extends Number,U extends Number> Double[][] mul(final T[][] tt,final U u){
        final var uu = RPTA(1,A(u));
        return mul(tt,uu);
    }

    public static <T extends Number,U extends Number> Double[][] div(final T[][] tt,final U[][] uu){
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1,shape_u._1),Math.max(shape_t._2,shape_u._2));
        return ij_yield(shape._1,shape._2,(i,j)->
                tt[i%shape_t._1][j%shape_t._2].doubleValue() /
                uu[i%shape_u._1][j%shape_u._2].doubleValue(),
                Double.class);
    }

    public static <T extends Number,U extends Number> Double[][] div(final T[][] tt,final U u){
        final var uu = RPTA(1,A(u));
        return div(tt,uu);
    }
    
    public static <T extends Number,U extends Number> Double[][] div2(final T[][] tt,final U[][] uu){
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1,shape_u._1),Math.max(shape_t._2,shape_u._2));
        return ij_yield(shape._1,shape._2,(i,j)->
                uu[i%shape_u._1][j%shape_u._2].doubleValue()/
                tt[i%shape_t._1][j%shape_t._2].doubleValue(), 
                Double.class);
    }

    public static <T extends Number,U extends Number> Double[][] div2(final T[][] tt,final U u){
        final var uu = RPTA(1,A(u));
        return div2(tt,uu);
    }
    
    public static <T extends Number> Double[][] adjugate(final T[][] tt){
        final var shape = shape(tt);
        return ij_yield(shape._1,shape._2,(i,j)->det(minor(tt,j,i))*((i+j)%2==0?1:-1),Double.class);
    }

    public static <T extends Number> Double[][] inverse(final  T[][] tt){
        return div(adjugate(tt),det(tt));
    }

    public static <T extends Number> List<Double[]> schmidt(final List<T[]> alphas) {
        final var betas = new LinkedList<Double[]>();
        final var eps = 1e-10;// 微小忽略量
        final Predicate<Number[]> is_not_zero = aa -> S(aa).allMatch(e -> Math.abs(e.doubleValue()) >= eps);// 与微小忽略量进行比较
        alphas.stream().filter(is_not_zero)
            .map( alpha -> betas.stream()
                .map( beta -> mul(-dot(alpha, beta) / dot(beta, beta), beta))
                .reduce((x,y)->add(x,y)).map(e -> add(alpha, e))
                .orElse(S(alpha).map(e -> e.doubleValue()).toArray(Double[]::new))
            ).filter(is_not_zero).forEach(betas::add);
        return betas;
    }
    
    public static <T> String fmt(final T[][] tt){
        return S(tt).map(t->S(t).map(e->e.toString()).collect(Collectors.joining("\t")))
                .collect(Collectors.joining("\n"));
    }

    public static <T> String fmt(final T[] tt){
        return Arrays.asList(tt).toString();
    }

    public static <T> String fmt2(final T[][] tt){
        final var line =  fmt(tt);
        System.out.println(line);
        return line;
    }

    public static <T> String fmt2(final T[] tt){
        final var line =  fmt(tt);
        System.out.println(line);
        return line;
    }

    @SuppressWarnings("unchecked")
    public static <T> T[] RPTA(final int n,final T t){
        return stm(n).map(i->t).toArray(arr((Class<T>) t.getClass()));
    }

    @Test
    public void foo(){
        final var digits = "0123456789".split("");
        cph2(L(digits,digits,digits)).forEach(System.out::println);
        final var tt = S(1,2,3,4,5,4).collect(mxclc2(10,10));
        System.out.println(fmt(tt));
        final var pp = S(1,2,1,2).collect(mxclc(2,2));
        System.out.println(fmt(mmult(pp,pp)));
        final var qq = S(1,2,3,4).collect(mxclc2(2,2));
        System.out.println(det(qq));
        System.out.println(fmt(qq));
        System.out.println(fmt(mmult(inverse(qq),qq)));
        
        final var aa = A(1, 2);
        final var bb = A(aa, aa);
        System.out.println("mmult:\n"+fmt(mmult(bb, bb)));
        System.out.println("-------schmidt--------");
        schmidt(L(aa, aa)).forEach(b -> {
            System.out.println(Arrays.asList(b));
        });
    }
    
    @Test
    public void qux() {
        final var obs = S(1,2,3,4).collect(mxclc(10,10));// the raw data
        fmt2(mmult(obs,RPTA(1,A(1))));
        final var _objs = S(obs).map(o->CONS(1,o)).toArray(arr2(Number.class));// the extended data
        final var wts = V(1,2,3,4,5,6,7,8,9,10);
        final var shape = shape(obs);
        final var Y = ij_yield(shape._1,1,(i,j)->obs[i][0]);
        S(sub(Y,mmult(_objs,wts)))
        .map(e->dot(e,e))
        .forEach(t->{
            System.out.println(t);
        });
    }

}
